package com.hsn.swing.geotools;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import org.geotools.data.ows.CRSEnvelope;
import org.geotools.data.ows.Layer;
import org.geotools.data.ows.OperationType;
import org.geotools.data.ows.StyleImpl;
import org.geotools.data.wms.WMSUtils;
import org.geotools.data.wms.WebMapServer;
import org.geotools.data.wms.request.GetMapRequest;


/**
 * This lab explores the use of the GeoTools WMS client code adapted by Jody
 * Garnett's demo.
 * 
 * @author Hasan Turksoy
 */
public class WMSLab extends JFrame {
	private static final long serialVersionUID = -3039255518595806472L;

	/** Original coverage we are working on */
	WebMapServer wms;
	CoveragePanel panel;
	Image image;
	JList layers;
	JButton getMapButton;
	JComboBox styleCombo;
	/**
	 * Map<Layer,Style> as controlled by styleCombo box
	 */
	Map styles = new HashMap();
	Layer selectedLayer = null;

	DefaultComboBoxModel availableStyles;

	/**
	 * Explore the functionality of the provided GridCoverage (think
	 * BufferedImage + CRS).
	 * <p>
	 * A GridCoverage literally a set of features that "covers" an area without
	 * gaps; in the case of grid coverage the area is covered by an regular
	 * grid.
	 * <p>
	 * Coverage work by letting you call a "sample" operation in order to
	 * retrieve a Record of the data at the location. A grid coverage lets you
	 * express the location using row and column.
	 * <p>
	 * 
	 * @param coverage
	 */
	public WMSLab(WebMapServer server) {
		this.wms = server;
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setLayout(new BorderLayout());

		String title = getWMSTitle(wms);
		setTitle(title);

		Set good = WMSUtils.getQueryableLayers(wms.getCapabilities());
		this.layers = new JList(good.toArray());
		layers.setCellRenderer(new LayerCellRenderer());
		layers.getSelectionModel().addListSelectionListener(
				new ListSelectionListener() {
					public void valueChanged(ListSelectionEvent e) {
						setSelectedLayer((Layer) layers.getSelectedValue());
					}
				});
		JScrollPane scrollPane = new JScrollPane(layers);
		scrollPane.setPreferredSize(new Dimension(200, 400));
		add(scrollPane, BorderLayout.WEST);

		this.panel = new CoveragePanel();
		add(panel, BorderLayout.CENTER);

		JPanel buttons = new JPanel();
		getMapButton = new JButton("GetMap");
		getMapButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				getMap();
			}
		});
		buttons.add(getMapButton);
		availableStyles = new DefaultComboBoxModel();
		styleCombo = new JComboBox(availableStyles);
		styleCombo.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent e) {
				if (selectedLayer != null) {
					styles.put(selectedLayer, styleCombo.getSelectedItem());
				}
			}
		});
		styleCombo.setRenderer(new StyleCellRenderer());
		buttons.add(styleCombo);

		add(buttons, BorderLayout.NORTH);
		pack();
	}

	private String getWMSTitle(WebMapServer wms) {
		return null;
	}

	public void setSelectedLayer(Layer selected) {
		availableStyles.removeAllElements();
		this.selectedLayer = selected;
		if (selected != null) {
			// availableStyles.addElement("default");
			List styles = selected.getStyles();
			for (Iterator i = styles.iterator(); i.hasNext();) {
				StyleImpl style = (StyleImpl) i.next();
				availableStyles.addElement(style);
			}
		}
		styleCombo.repaint();
	}

	class StyleCellRenderer extends DefaultListCellRenderer {
		private static final long serialVersionUID = 7421698429520525469L;

		public Component getListCellRendererComponent(JList list, Object value, // value
				// to
				// display
				int index, // cell index
				boolean iss, // is the cell selected
				boolean chf) // the list and the cell have the focus
		{
			super.getListCellRendererComponent(list, value, index, iss, chf);
			StyleImpl style = (StyleImpl) value;
			if (style == null)
				return this;

			String title = style.getTitle() == null ? style.getName() : style
					.getTitle().toString();
			setText(title);
			return this;
		}

		Icon getIcon(StyleImpl style) {
			List urlList = style.getLegendURLs();
			if (urlList == null || urlList.isEmpty())
				return null;

			URL url = (URL) style.getLegendURLs().get(0);
			ImageIcon icon = new ImageIcon(url);
			return icon;
		}
	}

	class LayerCellRenderer extends DefaultListCellRenderer {
		private static final long serialVersionUID = 1173012107250651733L;
		HashMap icons = new HashMap();

		public Component getListCellRendererComponent(JList list, Object value, // value
				// to
				// display
				int index, // cell index
				boolean iss, // is the cell selected
				boolean chf) // the list and the cell have the focus
		{
			super.getListCellRendererComponent(list, value, index, iss, chf);
			Layer layer = (Layer) value;
			setText(layer.getTitle());
			Icon glyph;
			if (icons.containsKey(layer)) {
				glyph = (Icon) icons.get(layer);
			} else {
				glyph = getLegendGraphics(layer);
				icons.put(layer, glyph);
			}
			setIcon(glyph);
			setToolTipText(layer.getName());
			return this;
		}
	}

	public Icon getLegendGraphics(Layer layer) {
		return null;
	}

	private String getNamedStyle(Layer layer) {
		Object layerStyle = styles.get(layer);
		String namedStyle = null;
		if (layerStyle == null) {
			namedStyle = null;
		} else if (layerStyle instanceof String) {
			namedStyle = (String) layerStyle;

		} else if (layerStyle instanceof StyleImpl) {
			StyleImpl style = (StyleImpl) layerStyle;
			namedStyle = style.getName();
		}
		return namedStyle;
	}

	public void getMap() {
        try {
            Layer selected = (Layer) layers.getSelectedValue();
            
            List<Layer> layers = new ArrayList<Layer>(1);
            layers.add(selected);
            
            getMap(layers);
        } catch (Exception e1) {
            image = null;
        }
    }

	private void getMap(Layer layer) throws Exception {
		panel.repaint();
	}

	private void getMap(List layers) throws Exception {
		if (layers == null || layers.isEmpty())
			return;

		GetMapRequest mapRequest = wms.createGetMapRequest();
		CRSEnvelope bounds = null;
		for (Iterator i = layers.iterator(); i.hasNext();) {
			Layer layer = (Layer) i.next();
			String namedStyle = getNamedStyle(layer);
			mapRequest.addLayer(layer, namedStyle);
			CRSEnvelope box = layer.getLatLonBoundingBox();
			box.setEPSGCode("EPSG:4326");
			if (box != null) {
				if (bounds != null) {
					bounds.setMinX(Math.min(bounds.getMinX(), box.getMinX()));
					bounds.setMaxX(Math.max(bounds.getMaxX(), box.getMaxX()));
					bounds.setMinY(Math.min(bounds.getMinY(), box.getMinY()));
					bounds.setMaxY(Math.max(bounds.getMaxY(), box.getMaxY()));
				} else {
					bounds = box;
				}
			}
		}
		if (bounds == null) {
			bounds = new CRSEnvelope();
			bounds.setEPSGCode("EPSG:4326");
		}
		mapRequest.setFormat(getImageFormat(wms));

		mapRequest.setSRS(bounds.getEPSGCode());
		mapRequest.setBBox(bounds);
		mapRequest.setDimensions(panel.getWidth(), panel.getHeight());

		URL url = mapRequest.getFinalURL();
		System.out.println("requesting layers : " + url);
		ImageIcon load = new ImageIcon(url);
		image = load.getImage();
		panel.repaint();
	}

	/**
	 * Some WMS Servers like GeoServer support strange formats like KML and PDF
	 * 
	 * @return
	 */
	String getImageFormat(WebMapServer wms) {
		OperationType description = wms.getCapabilities().getRequest()
				.getGetMap();

		List formats = description.getFormats();
		for (Iterator i = formats.iterator(); i.hasNext();) {
			String format = (String) i.next();
			if (format.indexOf("pdf") != -1)
				continue;

			return format;
		}
		return "image/jpeg";
	}

	/**
	 * Search through the layer data structure for bounds for the provided srs
	 * (if null we will return the first bounds).
	 * 
	 * @param srs
	 * @return CRSEnvelope
	 */
	CRSEnvelope getCRSEnvelope(Layer layer, String srs) {

		if (layer == null)
			return null;
		Map boundingBoxes = layer.getBoundingBoxes();
		if (boundingBoxes != null && !boundingBoxes.isEmpty()) {
			for (Iterator i = boundingBoxes.entrySet().iterator(); i.hasNext();) {
				Map.Entry entry = (Entry) i.next();
				String layerSrs = (String) entry.getKey();
				CRSEnvelope box = (CRSEnvelope) entry.getValue();
				if (srs == null || srs.equals(layerSrs)) {
					return box;
				}
			}
		}
		return getCRSEnvelope(layer.getParent(), srs);
	}

	class CoveragePanel extends JPanel {
		private static final long serialVersionUID = -4755270758709990530L;

		CoveragePanel() {
			setBackground(Color.WHITE);
		}

		public Dimension getPreferredSize() {
			return new Dimension(640, 400);
		}

		public void paintComponent(Graphics graphics) {
			super.paintComponents(graphics);
			if (image != null) {
				Graphics2D g = (Graphics2D) graphics;
				g.drawImage(image, 0, 0, null);
			}
		}
	}

	/**
	 * Prompt the user for a file and open up ImageLab.
	 * 
	 * @param args
	 *            filename of image
	 */
	public static void main(String[] args) throws Exception {
		URL server = getServerURL(args);
		WebMapServer wms;

		System.out.println("Connecting to " + server);
		wms = new WebMapServer(server);

		System.out.println("Welcome");
		WMSLab wmsLab = new WMSLab(wms);
		wmsLab.setVisible(true);
	}

	public static String[] getLayerNames(WebMapServer wms) {
		Layer[] namedLayers = WMSUtils.getNamedLayers(wms.getCapabilities());
		String[] names = new String[namedLayers.length];
		for (int i = 0; i < namedLayers.length; i++) {
			Layer layer = namedLayers[i];
			names[i] = layer.getName();
		}
		return names;
	}

	public static URL getServerURL(String[] args) throws MalformedURLException {
		if (args.length == 0) {
			Object[] servers = new Object[] {
					"http://localhost:8080/geoserver/wms?service=WMS&request=GetCapabilities",
					"http://www2.dmsolutions.ca/cgi-bin/mswms_gmap?Service=WMS&VERSION=1.1.0&REQUEST=GetCapabilities",
					"http://wms.jpl.nasa.gov/wms.cgi?Service=WMS&Version=1.1.1&Request=GetCapabilities",
					"http://giswebservices.massgis.state.ma.us/geoserver/wms?service=WMS&request=GetCapabilities",
					"http://wms.cits.rncan.gc.ca/cgi-bin/cubeserv.cgi?VERSION=1.1.0&REQUEST=GetCapabilities",
					"http://atlas.gc.ca/cgi-bin/atlaswms_en?VERSION=1.1.1&Request=GetCapabilities&Service=WMS", };
			Object selected = JOptionPane.showInputDialog(null,
					"WMS GetCapabilities URL", "Choose a WMS Server",
					JOptionPane.QUESTION_MESSAGE, null, servers, null);

			if (selected == null)
				System.exit(0);

			return new URL((String) selected);
		} else {
			return new URL(args[0]);
		}
	}
}